﻿using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Text;

namespace YaTools.Yaml
{
    internal struct BufferSegment : IEquatable<BufferSegment>
    {
        internal readonly char[] Buffer;
        internal readonly int Hash;
        internal readonly int Start;
        internal readonly int _segmentLength;
        private string _cachedString;

        public BufferSegment(char[] buffer, int start, int segmentLength)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(start >= 0);
            Contract.Requires(segmentLength >= 0);
            Contract.Assume(start + segmentLength <= buffer.Length, "start + length too big for buffer");

            Buffer = buffer;
            Start = start;
            _segmentLength = segmentLength;
            Hash = 0;
            _cachedString = null;

            Hash = InternalGetHashCode();
        }

        // ReSharper disable UnusedMember.Local
        [ContractInvariantMethod]
        private void ObjectInvariant()
        {
            Contract.Invariant(Buffer != null);
            Contract.Invariant(Start >= 0);
            Contract.Invariant(_segmentLength >= 0);
            Contract.Invariant(Start + _segmentLength <= Buffer.Length);
        }

        // ReSharper restore UnusedMember.Local

        [Pure]
        public override string ToString()
        {
            ValidateHash();
            return _cachedString ?? (_cachedString = new string(Buffer, Start, _segmentLength));
        }

        public override int GetHashCode()
        {
            ValidateHash();
            return Hash;
        }

        public bool Equals(BufferSegment other)
        {
            if (other.Buffer == null) return false;

            ValidateHash();
            other.ValidateHash();

            if (this.Hash != other.Hash) return false;
            if (this._segmentLength != other._segmentLength) return false;

            for (int i = 0; i < this._segmentLength; i++)
            {
                if (this.Buffer[i + this.Start] != other.Buffer[i + other.Start]) return false;
            }
            return true;
        }

        public override bool Equals(object obj)
        {
            if (obj is BufferSegment)
            {
                ValidateHash();
                var other = (BufferSegment) obj;
                return this.Equals(other);
            }
            return false;
        }

        public int SegmentLength
        {
            get { return _segmentLength; }
        }

        public char this[int index]
        {
            get
            {
                if (index < 0 || index >= _segmentLength) throw new IndexOutOfRangeException();
                return Buffer[index + Start];
            }
        }


        [Pure]
        private int InternalGetHashCode()
        {
            // Using the same basic hash algroithm as .NET string, on the 
            // assumption that it is a "good" hash function", 
            // _not_ that they are compatable

            int hash1 = 5381;
            int hash2 = hash1;

            int limit = Start + _segmentLength;
            for (int i = Start; i < limit; i++)
            {
                var c = (int) Buffer[i];
                hash1 = ((hash1 << 5) + hash1) ^ c;

                if (++i < limit)
                {
                    c = Buffer[i];
                    hash2 = ((hash2 << 5) + hash2) ^ c;
                }
            }

            return hash1 + (hash2*1566083941);
        }

        [Pure]
        [Conditional("DEBUG")]
        private void ValidateHash()
        {
            if (Hash != InternalGetHashCode())
            {
                throw new InvalidOperationException(
                    "Buffer contents changed while a BufferSegment was referencing it.");
            }
        }

        internal void AppendTo(StringBuilder sb)
        {
            Contract.Requires(sb != null);
            sb.Append(this.Buffer, this.Start, this._segmentLength);
        }
    }


    internal static class StringBuilderExtensions
    {
        public static void Append(this StringBuilder sb, BufferSegment segment)
        {
            Contract.Requires(sb != null);
            segment.AppendTo(sb);
        }
    }
}